Skip to content

Vector length diving + Farkas diving#1401

Open
nguidotti wants to merge 6 commits into
NVIDIA:mainfrom
nguidotti:new-diving-heuristics
Open

Vector length diving + Farkas diving#1401
nguidotti wants to merge 6 commits into
NVIDIA:mainfrom
nguidotti:new-diving-heuristics

Conversation

@nguidotti

@nguidotti nguidotti commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Implemented Farkas [1] and vector length [Section 9.2.6, 2] diving heuristics. It includes #1364.

Farkas diving is designed to move the current LP relaxation in direction to a valid Farkas proof. It pushes all variables towards the so-called pseudo solution, in which each variable assumes the best bound with respect to its objective coefficient as the solution value. Generally, this is very optimistic and most of the time, it will violate the constraints of the problem. Yet, if this finds a feasible solution, then it is expected to be very good.

Vector length diving is tailored for set partitioning and set covering. It chooses a rounding that coverst the largest number of constraints with the smallest objective value deterioration.

Benchmark Results

MIPLIB2017, 10min, GH200

All

================================================================================
 main-2026-06-01 (1) vs new-diving-heuristics (2)
================================================================================

------------------------------------------------------------------------------------------------------------------------------
|                                        |       Run 1        |       Run 2        |     Abs. Diff.     |   Rel. Diff. (%)   |
------------------------------------------------------------------------------------------------------------------------------
| Imported                                                 240                  240                   +0                 --- |
| Feasible                                                 226                  227                   +1                 --- |
| Optimal                                                   83                   84                   +1                 --- |
| Solutions with <0.1% primal gap                          134                  139                   +5                 --- |
| Nodes explored (mean)                              1.329e+07            1.295e+07           -3.335e+05               -2.51 |
| Nodes explored (shifted geomean)                   1.426e+04            1.372e+04               -539.2               -3.78 |
| Relative MIP gap (mean)                               0.2893               0.2854            -0.003857               -1.33 |
| Relative MIP gap (shifted geomean)                   0.09705              0.09449            -0.002568               -2.65 |
| Solve time (mean)                                      426.6                428.7               +2.022              +0.474 |
| Solve time (shifted geomean)                           202.4                207.1               +4.726               +2.33 |
| Primal gap (mean)                                      11.31                10.89              -0.4276               -3.78 |
| Primal gap (shifted geomean)                          0.5319               0.4957             -0.03619                -6.8 |
| Primal integral (mean)                                 32.45                31.21               -1.242               -3.83 |
| Primal integral (shifted geomean)                      6.582                6.147              -0.4357               -6.62 |
------------------------------------------------------------------------------------------------------------------------------

Vector length diving

================================================================================
 main-2026-06-01 (1) vs vector-length-diving (2)
================================================================================

------------------------------------------------------------------------------------------------------------------------------
|                                        |       Run 1        |       Run 2        |     Abs. Diff.     |   Rel. Diff. (%)   |
------------------------------------------------------------------------------------------------------------------------------
| Imported                                                 240                  240                   +0                 --- |
| Feasible                                                 226                  228                   +2                 --- |
| Optimal                                                   83                   84                   +1                 --- |
| Solutions with <0.1% primal gap                          134                  136                   +2                 --- |
| Nodes explored (mean)                              1.329e+07            1.285e+07           -4.331e+05               -3.26 |
| Nodes explored (shifted geomean)                   1.426e+04            1.312e+04                -1134               -7.95 |
| Relative MIP gap (mean)                               0.2893               0.3086             +0.01929               +6.67 |
| Relative MIP gap (shifted geomean)                   0.09705              0.09751            +0.000458              +0.472 |
| Solve time (mean)                                      426.6                421.3               -5.318               -1.25 |
| Solve time (shifted geomean)                           202.4                195.7                -6.75               -3.33 |
| Primal gap (mean)                                      11.31                10.47              -0.8411               -7.43 |
| Primal gap (shifted geomean)                          0.5319               0.5132             -0.01875               -3.53 |
| Primal integral (mean)                                 32.45                33.07              +0.6235               +1.92 |
| Primal integral (shifted geomean)                      6.582                 6.62             +0.03761              +0.571 |
------------------------------------------------------------------------------------------------------------------------------

Farkas diving

================================================================================
 main-2026-06-01 (1) vs farkas-diving (2)
================================================================================

------------------------------------------------------------------------------------------------------------------------------
|                                        |       Run 1        |       Run 2        |     Abs. Diff.     |   Rel. Diff. (%)   |
------------------------------------------------------------------------------------------------------------------------------
| Imported                                                 240                  240                   +0                 --- |
| Feasible                                                 226                  228                   +2                 --- |
| Optimal                                                   83                   84                   +1                 --- |
| Solutions with <0.1% primal gap                          134                  132                   -2                 --- |
| Nodes explored (mean)                              1.329e+07            1.322e+07            -6.36e+04              -0.479 |
| Nodes explored (shifted geomean)                   1.426e+04            1.436e+04               +107.1              +0.751 |
| Relative MIP gap (mean)                               0.2893                0.291            +0.001754              +0.606 |
| Relative MIP gap (shifted geomean)                   0.09705              0.09512            -0.001935               -1.99 |
| Solve time (mean)                                      426.6                429.4               +2.721              +0.638 |
| Solve time (shifted geomean)                           202.4                205.5                 +3.1               +1.53 |
| Primal gap (mean)                                      11.31                10.46              -0.8573               -7.58 |
| Primal gap (shifted geomean)                          0.5319               0.5099             -0.02204               -4.14 |
| Primal integral (mean)                                 32.45                33.11              +0.6652               +2.05 |
| Primal integral (shifted geomean)                      6.582                6.342              -0.2408               -3.66 |
------------------------------------------------------------------------------------------------------------------------------

References

[1] J. Witzig and A. Gleixner, “Conflict-Driven Heuristics for Mixed Integer Programming,” Feb. 07, 2019, arXiv: arXiv:1902.02615. doi: 10.48550/arXiv.1902.02615.

[2] T. Achterberg, “Constraint Integer Programming,” PhD, Technischen Universität Berlin, Berlin, 2007. doi: 10.14279/depositonce-1634.

Checklist

  • I am familiar with the Contributing Guidelines.
  • Testing
    • New or existing tests cover these changes
    • Added tests
    • Created an issue to follow-up
    • NA
  • Documentation
    • The documentation is up to date with these changes
    • Added new documentation
    • NA

nguidotti added 4 commits June 2, 2026 12:36
…udo_costs, worker and diving_heuristics.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…derabbit comments.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
@nguidotti nguidotti added this to the 26.08 milestone Jun 8, 2026
@nguidotti nguidotti self-assigned this Jun 8, 2026
@nguidotti nguidotti requested a review from a team as a code owner June 8, 2026 07:55
@nguidotti nguidotti requested a review from rg20 June 8, 2026 07:55
@nguidotti nguidotti added the non-breaking Introduces a non-breaking change label Jun 8, 2026
@nguidotti nguidotti requested a review from Kh4ster June 8, 2026 07:55
@nguidotti nguidotti added improvement Improves an existing functionality mip labels Jun 8, 2026
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces two new MIP diving heuristics—Farkas and vector-length strategies—by adding a templated configuration struct, enumerating new search strategies, implementing the selection logic, instrumenting the LP problem with coefficient magnitudes, integrating strategy dispatch into branch-and-bound, and registering runtime parameters in solver settings.

Changes

Diving Heuristics Feature

Layer / File(s) Summary
Diving hyper-parameters data contract
cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp, cpp/include/cuopt/linear_programming/constants.h
New mip_diving_hyper_params_t<i_t, f_t> struct template holds strategy enables (with auto/-1 semantics), depth/node/iteration/backtrack limits, Farkas dynamism tolerance, and log-display flag, exposed via parameter string macros.
Search strategy enumeration and enablement utilities
cpp/src/branch_and_bound/constants.hpp, cpp/src/branch_and_bound/diving_heuristics.hpp
search_strategy_t enum and search_strategies array expand from 5 to 7 strategies; feasible_solution_symbol() maps FARKAS_DIVING and VECTOR_LENGTH_DIVING to letter codes; is_search_strategy_enabled() checks nonzero diving parameter values.
Farkas and vector-length diving implementations
cpp/src/branch_and_bound/diving_heuristics.cpp
farkas_diving() ranks fractional variables by objective-sign-driven bound distance; vector_length_diving() ranks by fractional magnitude over column length; pseudocost_diving and guided_diving gain debug logging of selection details; both new heuristics instantiated for int, double.
LP problem coefficient magnitude tracking
cpp/src/dual_simplex/presolve.hpp
lp_problem_t gains max_abs_obj_coeff and min_abs_obj_coeff fields to enable runtime objective dynamism checks during Farkas strategy selection.
Branch-and-bound diving strategy routing and control flow
cpp/src/branch_and_bound/branch_and_bound.cpp
best_first_search_with() conditionally disables guided and Farkas diving based on incumbent availability and objective coefficient ratio; nondeterministic and deterministic variable selection dispatch FARKAS_DIVING and VECTOR_LENGTH_DIVING to new heuristic functions; feasible_solution_symbol() becomes runtime-controlled by show_type flag for solution reporting.
Configuration registration and solver settings wiring
cpp/src/math_optimization/solver_settings.cu
Diving hyperparameters (six strategy toggles, three limit controls, iteration factor, display flag) registered into solver_settings_t float/int/bool parameter registries with documented defaults and bounds.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • NVIDIA/cuopt#1364: Both PRs wire up the same exposed MIP diving hyper-parameters (new diving_hyper_params.hpp/constants and strategy-enable/log type helpers in diving_heuristics.*), so the main PR's additional branching/heuristic dispatch changes build directly on those shared interfaces.

Suggested reviewers

  • rgsl888prabhu
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description is directly related to the changeset, explaining the purpose and design of the two diving heuristics, providing benchmark results, and referencing academic sources.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the main changes: addition of two new diving heuristics (Vector length diving and Farkas diving) that are the primary focus of the pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
cpp/src/branch_and_bound/pseudo_costs.hpp (1)

32-37: 🏗️ Heavy lift

Forward declarations here conflict with the repository header policy.

This header now depends on forward declarations for branch_and_bound_worker_t/branch_and_bound_stats_t. Please refactor the include graph (for example by splitting strategy-enable utilities into a lighter header) so this header can include concrete dependencies instead of forward-declaring them.

As per coding guidelines: "Avoid forward declarations in favor of including headers in C++."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/branch_and_bound/pseudo_costs.hpp` around lines 32 - 37, The forward
declarations of branch_and_bound_worker_t and branch_and_bound_stats_t in
pseudo_costs.hpp violate the header policy; replace these forward declarations
by including the concrete headers that define branch_and_bound_worker_t and
branch_and_bound_stats_t (or refactor by extracting the minimal strategy
utilities used by pseudo_costs.hpp into a lightweight header that both this file
and the full worker/stats headers can include). Update pseudo_costs.hpp to
`#include` the new/lightweight header (or the original concrete headers) so the
types are available without forward declarations and adjust any include guards
or dependencies accordingly.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/include/cuopt/linear_programming/constants.h`:
- Around line 117-130: The constants header is missing the config key for the
parameter defined as farkas_obj_dynamism_tol in diving_hyper_params.hpp; add a
matching string constant (e.g., CUOPT_MIP_HYPER_DIVING_FARKAS_OBJ_DYNAMISM_TOL)
to cpp/include/cuopt/linear_programming/constants.h next to the other
diving-related defines so the config loader can find
"mip_hyper_diving_farkas_obj_dynamism_tol"; ensure the macro name matches the
naming pattern used for other keys (CUOPT_MIP_HYPER_DIVING_*) and the string
matches the parameter name used in diving_hyper_params.hpp.

In `@cpp/src/branch_and_bound/branch_and_bound.cpp`:
- Around line 1754-1757: Guard the division by checking objective magnitudes
before computing obj_dyn: if original_lp_.max_abs_obj_coeff == 0 set
diving_settings.farkas_diving = 0 (all-zero objective -> treat as low dynamism),
else only compute obj_dyn = log10(max/min) when original_lp_.min_abs_obj_coeff >
0 (use a tiny epsilon if you prefer to treat near-zero as zero); if min <= 0
skip the log10 check (leave farkas_diving enabled) to avoid inf/NaN, and keep
the existing comparison against diving_settings.farkas_obj_dynamism_tol when
obj_dyn is valid. Ensure you reference original_lp_.max_abs_obj_coeff,
original_lp_.min_abs_obj_coeff, diving_settings.farkas_diving, and
diving_settings.farkas_obj_dynamism_tol in the fix.

In `@cpp/src/branch_and_bound/diving_heuristics.cpp`:
- Around line 292-299: The Farkas score uses the wrong post-branch distances:
when dir == branch_direction_t::UP the branch bounds fix x_j >=
std::ceil(solution[j]) so compute remaining distance as lp.upper[j] -
std::ceil(solution[j]) (not lp.upper[j] - std::floor(solution[j])), and when dir
== branch_direction_t::DOWN use std::floor(solution[j]) - lp.lower[j] (not
std::ceil(solution[j]) - lp.lower[j]); update the calculation of score (the
block around score, lp.upper, lp.lower, solution[j], f_up, f_down, c, and f_t)
to use these post-branch bounds and keep the infinity fallback for non-finite
bounds.

In `@cpp/src/math_optimization/solver_settings.cu`:
- Line 175: The current option for CUOPT_MIP_HYPER_DIVING_BACKTRACK_LIMIT
exposes a raw upper bound of std::numeric_limits<i_t>::max(), but code
calculating the DFS stack uses (diving_backtrack_limit + 4) and can overflow;
change the option's maximum to a safe cap such as
(std::numeric_limits<i_t>::max() - 4) (or define a named constant like
MAX_SAFE_BACKTRACK = std::numeric_limits<i_t>::max() - 4) and use that instead
for the max value of mip_settings.diving_params.backtrack_limit so adding 4
cannot overflow.

---

Nitpick comments:
In `@cpp/src/branch_and_bound/pseudo_costs.hpp`:
- Around line 32-37: The forward declarations of branch_and_bound_worker_t and
branch_and_bound_stats_t in pseudo_costs.hpp violate the header policy; replace
these forward declarations by including the concrete headers that define
branch_and_bound_worker_t and branch_and_bound_stats_t (or refactor by
extracting the minimal strategy utilities used by pseudo_costs.hpp into a
lightweight header that both this file and the full worker/stats headers can
include). Update pseudo_costs.hpp to `#include` the new/lightweight header (or the
original concrete headers) so the types are available without forward
declarations and adjust any include guards or dependencies accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b33259e4-7a93-4f0f-9856-e8d5aea35845

📥 Commits

Reviewing files that changed from the base of the PR and between 2384454 and 414d940.

📒 Files selected for processing (15)
  • cpp/include/cuopt/linear_programming/constants.h
  • cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp
  • cpp/include/cuopt/linear_programming/mip/heuristics_hyper_params.hpp
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/branch_and_bound/branch_and_bound.cpp
  • cpp/src/branch_and_bound/constants.hpp
  • cpp/src/branch_and_bound/diving_heuristics.cpp
  • cpp/src/branch_and_bound/diving_heuristics.hpp
  • cpp/src/branch_and_bound/pseudo_costs.hpp
  • cpp/src/branch_and_bound/worker.hpp
  • cpp/src/dual_simplex/presolve.hpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip_heuristics/solver.cu
  • cpp/tests/mip/heuristics_hyper_params_test.cu

Comment thread cpp/include/cuopt/linear_programming/constants.h
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp
Comment thread cpp/src/branch_and_bound/diving_heuristics.cpp
Comment thread cpp/src/math_optimization/solver_settings.cu Outdated
nguidotti added 2 commits June 8, 2026 10:26
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
@nguidotti nguidotti requested review from akifcorduk, aliceb-nv and chris-maes and removed request for Kh4ster and rg20 June 9, 2026 11:53
@nguidotti nguidotti changed the title Vector length + Farkas diving Vector length diving + Farkas diving Jun 9, 2026

@akifcorduk akifcorduk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor comment, thanks for PR Nicolas!

}

if (dir == branch_direction_t::UP) {
score = std::isfinite(lp.upper[j])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't infinite scores come up often? Do you think using the largest variables upper bound in the problem to compute the scores of those upper=inf variables make sense? That way, you would create more balanced score rather than always blindly selecting upper=inf

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lp.upper[j] stores the variable upper bound for the variable $j$. If it is infinite, then we set the score to be infinite as well (otherwise, it will return nan operating with infinity). The first variable will infinite score will be selected (we are iterating with increasing indexes).

@nguidotti nguidotti Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean to use the max. value of the variable upper bound instead of infinity? This seems doable. We could also use a sentinel for infinity (SCIP do that, I think inf = 1E20)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, what I mean is to have some comparability in the scores. When a variables upper bound is infinite, it is chosen regardless of the objective coefficient and fractionality impact. For this variable, I would use the upper_bound as largest of the upper bounds among all variables (rough heuristics to determine cardinality). Then, other variables might also have chance to be chosen.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this goes against the idea of the paper: "From the primal point of view, the potential change in the objective function depends on how much a variable can be pushed until it reaches one of its bounds.".

If the bounds is infinite, then we can push the variable to infinite as well. We should prioritize it over the other variables to allow for cutting that specific branch as early in the tree as possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Improves an existing functionality mip non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants